//
//  PageCache.cpp
//  高并发内存池
//
//  Created by 卜绎皓 on 2023/2/14.
//

#include "PageCache.hpp"

//获取一个k页的span
Span* PageCache::NewSpan(size_t k)
{
    assert(k > 0 && k < NPAGES);
    
    //先检查第k个桶里面有没有span
    if (!_spanLists[k].Empty())
    {
        return _spanLists[k].PopFront();
    }
    
    //检查一下后面的桶里面有没有span，如果有可以将其进行切分
    for (size_t i = k + 1; i < NPAGES; i++)
    {
        if (!_spanLists[i].Empty())
        {
            Span* nSpan = _spanLists[i].PopFront();
            Span* kSpan = new Span;
            //在nSpan的头部切k页下来
            kSpan->_pageId = nSpan->_pageId;
            kSpan->_n = k;

            nSpan->_pageId += k;
            nSpan->_n -= k;
            //将剩下的挂到对应映射的位置
            _spanLists[nSpan->_n].PushFront(nSpan);
            
            
            //储存nSpan的首尾页号与nSpan之间的映射，方便page cache合并span时进行前后页的查找
            _idSpanMap[nSpan->_pageId] = nSpan;
            _idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;
            
            
            //建立页号与span的映射，方便central cache回收小块内存时查找对应的span
            for(PAGE_ID i = 0;i < kSpan->_n;++i)
            {
                _idSpanMap[kSpan->_pageId + i] = kSpan;
            }

            return kSpan;
        }
    }
    
    
    //走到这里说明后面没有大页的span了，这时就向堆申请一个128页的span
    Span* bigSpan = new Span;
    void* ptr = SystemAlloc(NPAGES - 1);
    bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
    bigSpan->_n = NPAGES - 1;

    _spanLists[bigSpan->_n].PushFront(bigSpan);

    //尽量避免代码重复，递归调用自己
    return NewSpan(k);
}

//获取从对象到span的映射
Span* PageCache::MapObjectToSpan(void *obj)
{
    PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT; // 页号
    auto ret = _idSpanMap.find(id);
    
    if(ret != _idSpanMap.end())
    {
        return ret->second;
    }
    else
    {
        assert(false);
        return nullptr;
    }
}

//释放空闲的span回收到pagecache，并且合并相邻的span
void PageCache::ReleaseSpanToPageCache(Span* span)
{
    //对span的前后页尝试进行合并，解决内存碎片问题
    //1. 向前合并
    while(1)
    {
        PAGE_ID prevId = span->_pageId - 1;
        auto ret = _idSpanMap.find(prevId);
        //前面的页号没有（还未向系统申请），停止向前合并
        if(ret == _idSpanMap.end())
        {
            break;
        }
        
        //前面的页号对应的span正在被使用，停止向前合并
        Span* prevSpan = ret->second;
        if(prevSpan->_isUes == true)
        {
            break;
        }
        
        //合并出超过128页的span无法进行管理，停止合并
        if(prevSpan->_n + span->_n > NPAGES - 1)
        {
            break;
        }
        
        //进行向前合并
        span->_pageId = prevSpan->_pageId;
        span->_n += prevSpan->_n;
        
        //将prevSpan从对应的双链表中移除
        _spanLists[prevSpan->_n].Erase(prevSpan);
        
        delete prevSpan;
    }
    
    //2. 向后合并
    while(1)
    {
        PAGE_ID nextId = span->_pageId + span->_n;
        auto ret = _idSpanMap.find(nextId);
        
        //后面的页号没有（还未向系统申请），停止向后合并
        if(ret == _idSpanMap.end())
        {
            break;
        }
        
        //后面的页号对应的span正在被使用，停止向后合并
        Span* nextSpan = ret->second;
        if(nextSpan->_isUes == true)
        {
            break;
        }
        
        //合并出超过128页的span无法进行管理，停止向后合并
        if(nextSpan->_n + span->_n > NPAGES - 1)
        {
            break;
        }
        
        //进行向后合并
        span->_n += nextSpan->_n;
        
        //将nextSpan从对应的双链表中移除
        _spanLists[nextSpan->_n].Erase(nextSpan);
        
        delete nextSpan;
    }
    
    //将合并后的span挂到对应的双链表当中
    _spanLists[span->_n].PushFront(span);
    //建立该span与其首尾页的映射
    _idSpanMap[span->_pageId] = span;
    _idSpanMap[span->_pageId + span->_n - 1] = span;
    //将该span设置为未使用状态
    span->_isUes = false;
    
}
